package com.luckyframe.project.jmeter.jmeterTestPlan.controller;

import cn.hutool.core.thread.ThreadUtil;
import com.alibaba.fastjson.JSON;
import com.luckyframe.common.constant.ScheduleConstants;
import com.luckyframe.common.support.Convert;
import com.luckyframe.common.utils.StringUtils;
import com.luckyframe.common.utils.poi.ExcelUtil;
import com.luckyframe.common.utils.security.ShiroUtils;
import com.luckyframe.framework.aspectj.lang.annotation.Log;
import com.luckyframe.framework.aspectj.lang.enums.BusinessType;
import com.luckyframe.framework.config.JmeterConfig;
import com.luckyframe.framework.web.controller.BaseController;
import com.luckyframe.framework.web.domain.AjaxResult;
import com.luckyframe.framework.web.page.TableDataInfo;
import com.luckyframe.project.jmeter.consumer.watch.WatchTaskManager;
import com.luckyframe.project.jmeter.jmeterNode.domain.JmeterNode;
import com.luckyframe.project.jmeter.jmeterNode.enums.JmeterNodeStatus;
import com.luckyframe.project.jmeter.jmeterNode.service.IJmeterNodeService;
import com.luckyframe.project.jmeter.jmeterNodeJobRel.domain.JmeterNodeJobRel;
import com.luckyframe.project.jmeter.jmeterNodeJobRel.service.IJmeterNodeJobRelService;
import com.luckyframe.project.jmeter.jmeterPlanCaseRel.domain.JmeterPlanCaseRel;
import com.luckyframe.project.jmeter.jmeterPlanCaseRel.service.IJmeterPlanCaseRelService;
import com.luckyframe.project.jmeter.jmeterTaskScheduling.domain.JmeterTaskScheduling;
import com.luckyframe.project.jmeter.jmeterTaskScheduling.service.IJmeterTaskSchedulingService;
import com.luckyframe.project.jmeter.jmeterTestCase.domain.JmeterTestCase;
import com.luckyframe.project.jmeter.jmeterTestCase.service.IJmeterTestCaseService;
import com.luckyframe.project.jmeter.jmeterTestPlan.context.TestPlanListener;
import com.luckyframe.project.jmeter.jmeterTestPlan.domain.JmeterTestPlan;
import com.luckyframe.project.jmeter.jmeterTestPlan.domain.PlanCaseList;
import com.luckyframe.project.jmeter.jmeterTestPlan.domain.PlanCaseTask;
import com.luckyframe.project.jmeter.jmeterTestPlan.domain.PlanCaseTaskGroup;
import com.luckyframe.project.jmeter.jmeterTestPlan.enums.JmeterJobType;
import com.luckyframe.project.jmeter.jmeterTestPlan.exception.TestCaseCirculaException;
import com.luckyframe.project.jmeter.jmeterTestPlan.service.IJmeterTestPlanService;
import com.luckyframe.project.jmeter.jmeterTestPlanMetric.domain.JmeterTestPlanMetric;
import com.luckyframe.project.jmeter.jmeterTestPlanMetric.service.IJmeterTestPlanMetricService;
import com.luckyframe.project.jmeter.jmeterTestPlanRecord.enums.JmeterPlanRecordStatus;
import com.luckyframe.project.jmeter.jmeterTestPlanRecord.service.IJmeterTestPlanRecordService;
import com.luckyframe.project.jmeter.util.SeqGenerator;
import com.luckyframe.project.monitor.job.domain.Job;
import com.luckyframe.project.monitor.job.service.IJobService;
import com.luckyframe.project.system.project.domain.Project;
import com.luckyframe.project.system.project.service.IProjectService;
import com.luckyframe.project.testmanagmt.projectCaseModule.domain.ProjectCaseModule;
import com.luckyframe.project.testmanagmt.projectCaseModule.service.IProjectCaseModuleService;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.*;

import java.io.File;
import java.util.*;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

import static java.util.stream.Collectors.toList;

/**
 * jmx测试计划 信息操作处理
 *
 * @author lancelot
 * @date 2020-09-30
 */
@Controller
@RequestMapping("/jmeter/jmeterTestPlan")
public class JmeterTestPlanController extends BaseController {

    private static final Logger log = LoggerFactory.getLogger(JmeterTestPlanController.class);

    private String prefix = "jmeter/jmeterTestPlan";

    @Autowired
    private IJmeterTestPlanService jmeterTestPlanService;

    @Autowired
    private IJmeterTestCaseService jmeterTestCaseService;

    @Autowired
    private IProjectService projectService;

    @Autowired
    private IJmeterPlanCaseRelService jmeterPlanCaseRelService;

    @Autowired
    private IJmeterNodeService jmeterNodeService;

    @Autowired
    private JmeterConfig jmeterConfig;

    @Autowired
    private IJmeterTestPlanRecordService jmeterTestPlanRecordService;

    @Autowired
    private IJmeterTaskSchedulingService jmeterTaskSchedulingService;

    @Autowired
    private IProjectCaseModuleService projectCaseModuleService;

    @Autowired
    private IJmeterTestPlanMetricService jmeterTestPlanMetricService;

    @Autowired
    private IJobService jobService;

    @Autowired
    private WatchTaskManager watchTaskManager;

    @Autowired
    private IJmeterNodeJobRelService jmeterNodeJobRelService;

    private ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10, 100,
            0L, TimeUnit.MILLISECONDS,
            new LinkedBlockingQueue<Runnable>(), ThreadUtil.newNamedThreadFactory("jmeterExec", false));

    private final String DATE_CRON_FOMATTER = "ss mm HH dd MM ? YYYY";


    @RequiresPermissions("jmeter:jmeterTestPlan:view")
    @GetMapping()
    public String jmeterTestPlan(ModelMap mmap) {
        List<Project> projects = projectService.selectProjectAll(0);
        mmap.put("projects", projects);
        if (projects.size() > 0) {
            ProjectCaseModule projectCaseModule = new ProjectCaseModule();
            if (StringUtils.isNotEmpty(ShiroUtils.getProjectId())) {
                mmap.put("defaultProjectId", ShiroUtils.getProjectId());
                projectCaseModule = projectCaseModuleService.selectProjectCaseModuleParentZeroByProjectId(ShiroUtils.getProjectId());
            }
            mmap.put("projectCaseModule", projectCaseModule);
        }


        return prefix + "/jmeterTestPlan";
    }

    @GetMapping("/selectTestCase")
    public String projectPlanCase(@RequestParam("planId") Long planId, ModelMap mmap) {
        List<JmeterTestCase> cases = jmeterTestCaseService.selectJmeterTestCaseByPlanId(planId);
        List<JmeterPlanCaseRel> jmeterPlanCaseRels = jmeterPlanCaseRelService.listByPlanId(planId);
        if (CollectionUtils.isNotEmpty(jmeterPlanCaseRels)) {
            mmap.put("relCaseIdList", jmeterPlanCaseRels.stream().map(JmeterPlanCaseRel::getRelCaseId).collect(toList()));
        } else {
            mmap.put("relCaseIdList", new ArrayList<>());
        }
        mmap.put("cases", cases);
        return prefix + "/selectTestCase";
    }


    /**
     * 保存测试计划用例操作
     *
     * @author Seagull
     * @date 2019年9月30日
     */
    @RequestMapping(value = "/savePlanCase", method = RequestMethod.POST, consumes = "application/json")
    @ResponseBody
    public AjaxResult savePlanCase(@RequestBody PlanCaseList planCaseList) {

        Long planId = planCaseList.getPlanId();

        List<Long> relCaseIdList = planCaseList.getCaseIdList();
        jmeterPlanCaseRelService.updatePlanCaseIdList(relCaseIdList, planId);

        return toAjax(1);
    }


    /**
     * 查询jmx测试计划列表
     */
    @RequiresPermissions("jmeter:jmeterTestPlan:list")
    @PostMapping("/list")
    @ResponseBody
    public TableDataInfo list(JmeterTestPlan jmeterTestPlan) {
        startPage();
        List<JmeterTestPlan> list = jmeterTestPlanService.selectJmeterTestPlanList(jmeterTestPlan);

        if (CollectionUtils.isNotEmpty(list)) {
            for (JmeterTestPlan testPlan : list) {
                Job relatedJob = jmeterTestPlanService.getRelatedJobByPlanId(testPlan.getId());
                if (relatedJob != null) {
                    testPlan.setCronJobStatus(relatedJob.getStatus());
                }
            }
        }

        return getDataTable(list);
    }


    /**
     * 导出jmx测试计划列表
     */
    @RequiresPermissions("jmeter:jmeterTestPlan:export")
    @PostMapping("/export")
    @ResponseBody
    public AjaxResult export(JmeterTestPlan jmeterTestPlan) {
        List<JmeterTestPlan> list = jmeterTestPlanService.selectJmeterTestPlanList(jmeterTestPlan);
        ExcelUtil<JmeterTestPlan> util = new ExcelUtil<JmeterTestPlan>(JmeterTestPlan.class);
        return util.exportExcel(list, "jmeterTestPlan");
    }

    /**
     * 新增jmx测试计划
     */
    @GetMapping("/add")
    public String add(ModelMap mmap) {

        List<JmeterTestCase> cases = jmeterTestCaseService.selectJmeterTestCaseList(new JmeterTestCase());
        mmap.put("cases", cases);

        List<Project> projects = projectService.selectProjectAll(0);
        mmap.put("projects", projects);
        if (projects.size() > 0) {
            ProjectCaseModule projectCaseModule;
            if (StringUtils.isNotEmpty(ShiroUtils.getProjectId())) {
                mmap.put("defaultProjectId", ShiroUtils.getProjectId());
                projectCaseModule = projectCaseModuleService
                        .selectProjectCaseModuleParentZeroByProjectId(ShiroUtils.getProjectId());
            } else {
                projectCaseModule = projectCaseModuleService
                        .selectProjectCaseModuleParentZeroByProjectId(projects.get(0).getProjectId());
            }
            mmap.put("projectCaseModule", projectCaseModule);
        }


        return prefix + "/add";
    }

    /**
     * 新增保存jmx测试计划
     */
    @RequiresPermissions("jmeter:jmeterTestPlan:add")
    @Log(title = "jmx测试计划", businessType = BusinessType.INSERT)
    @PostMapping("/add")
    @ResponseBody
    public AjaxResult addSave(@RequestBody JmeterTestPlan jmeterTestPlan) {

        Integer jobType = jmeterTestPlan.getJobType();
        String cronExpr = getCronExpr(jmeterTestPlan, jobType);

        if (Objects.equals(jobType, JmeterJobType.CRON.getCode())) {
            jmeterTestPlan.setScheduledTime(null);
            jmeterTestPlan.setCronExpr(cronExpr);
        }

        if (Objects.equals(jobType, JmeterJobType.BY_TIME.getCode()) || Objects.equals(jobType, JmeterJobType.NOT_SET.getCode())) {
            jmeterTestPlan.setCronExpr("");
        }


        Integer relProjectId = jmeterTestPlan.getRelProjectId();
        if (relProjectId != null) {
            Project project = projectService.selectProjectById(relProjectId);
            if (project != null) {
                jmeterTestPlan.setRelProjectName(project.getProjectName());
            }
        }


        Long relModuleId = jmeterTestPlan.getRelModuleId();
        if (relModuleId != null) {
            ProjectCaseModule module = projectCaseModuleService.selectProjectCaseModuleById(relModuleId.intValue());
            if (module != null) {
                jmeterTestPlan.setRelModuleName(module.getModuleName());
            }
        }


        int reslut = jmeterTestPlanService.insertJmeterTestPlan(jmeterTestPlan);
        Long planId = jmeterTestPlan.getId();

        List<Long> relCaseIdList = jmeterTestPlan.getRelCaseIdList();

        if (CollectionUtils.isNotEmpty(relCaseIdList)) {
            JmeterPlanCaseRel planCaseRel = new JmeterPlanCaseRel();
            for (Long caseId : relCaseIdList) {
                planCaseRel.setRelCaseId(caseId);
                planCaseRel.setRelPlanId(Long.valueOf(planId));
                planCaseRel.setCreateTime(new Date());
                jmeterPlanCaseRelService.insertJmeterPlanCaseRel(planCaseRel);
            }
        }

        //cron 表达式
        if (Objects.equals(jobType, JmeterJobType.CRON.getCode()) || Objects.equals(jobType, JmeterJobType.BY_TIME.getCode())) {

            Job job = new Job();
            job.setJobName("jmeterCronTask");
            job.setJobGroup("JMETER自动化任务调度");
            job.setMethodName("runTask");
            job.setMethodParams(String.valueOf(planId));
            job.setCronExpression(cronExpr);
            job.setMisfirePolicy(ScheduleConstants.MISFIRE_DO_NOTHING);
            job.setStatus(ScheduleConstants.Status.NORMAL.getValue());
            job.setRemark("");
            jobService.insertJobCron(job);

            //create scheduler
            JmeterTaskScheduling jmeterTaskScheduling = new JmeterTaskScheduling();
            jmeterTaskScheduling.setPlanId(planId);
            jmeterTaskScheduling.setJobId(job.getJobId());
            jmeterTaskSchedulingService.insertJmeterTaskScheduling(jmeterTaskScheduling);
        }

        return toAjax(reslut);
    }

    /**
     * 修改jmx测试计划
     */
    @GetMapping("/edit/{id}")
    public String edit(@PathVariable("id") Long id, ModelMap mmap) {
        JmeterTestPlan jmeterTestPlan = jmeterTestPlanService.selectJmeterTestPlanById(id);

        if (jmeterTestPlan.getScheduledTime() != null) {
            jmeterTestPlan.setDate(DateFormatUtils.format(jmeterTestPlan.getScheduledTime(), "yyyy-MM-dd HH:mm:ss"));
        }

        mmap.put("jmeterTestPlan", jmeterTestPlan);

        List<JmeterTestCase> cases = jmeterTestCaseService.selectJmeterTestCaseList(new JmeterTestCase());
        mmap.put("cases", cases);


        List<JmeterPlanCaseRel> jmeterPlanCaseRels = jmeterPlanCaseRelService.listByPlanId(id);
        if (CollectionUtils.isNotEmpty(jmeterPlanCaseRels)) {
            mmap.put("relCaseIdList", jmeterPlanCaseRels.stream().map(JmeterPlanCaseRel::getRelCaseId).collect(toList()));
        } else {
            mmap.put("relCaseIdList", new ArrayList<>());
        }


        List<Project> projects = projectService.selectProjectList(new Project());
        mmap.put("projects", projects);


        return prefix + "/edit";
    }



    /**
     *
     */
    @PostMapping("/pauseJob")
    @ResponseBody
    public AjaxResult pauseJob(@RequestBody JmeterTestPlan plan) {

        /** 任务状态（0正常 1暂停） */
        Job job = jmeterTestPlanService.getRelatedJobByPlanId(plan.getId());
        if (job != null && Objects.equals(job.getStatus(), "0")) {
            jobService.pauseJob(job);
        }

        return AjaxResult.success();
    }

    /**
     *
     */
    @PostMapping("/resumeJob")
    @ResponseBody
    public AjaxResult resumeJob(@RequestBody JmeterTestPlan plan) {

        /** 任务状态（0正常 1暂停） */
        Job job = jmeterTestPlanService.getRelatedJobByPlanId(plan.getId());

        if (job != null && Objects.equals(job.getStatus(), "1")) {
            jobService.resumeJob(job);
        }

        return AjaxResult.success();
    }


    /**
     * 修改保存jmx测试计划
     */
    @RequiresPermissions("jmeter:jmeterTestPlan:edit")
    @Log(title = "jmx测试计划", businessType = BusinessType.UPDATE)
    @PostMapping("/edit")
    @ResponseBody
    public AjaxResult editSave(@RequestBody JmeterTestPlan jmeterTestPlan) {

        Integer jobType = jmeterTestPlan.getJobType();
        if (Objects.equals(jobType, JmeterJobType.CRON.getCode())) {
            jmeterTestPlan.setScheduledTime(null);
        }

        if (Objects.equals(jobType, JmeterJobType.BY_TIME.getCode())) {
            jmeterTestPlan.setCronExpr("");
        }


        if (Objects.equals(jobType, JmeterJobType.NOT_SET.getCode())) {
            jmeterTestPlan.setCronExpr("");
            jmeterTestPlan.setScheduledTime(null);
        }


        int i = jmeterTestPlanService.updateJmeterTestPlan(jmeterTestPlan);

        List<Long> relCaseIdList = jmeterTestPlan.getRelCaseIdList();
        Long planId = jmeterTestPlan.getId();
        jmeterPlanCaseRelService.updatePlanCaseIdList(relCaseIdList, planId);


        Job relatedJob = jmeterTestPlanService.getRelatedJobByPlanId(planId);

        //cron 表达式
        if (Objects.equals(jobType, JmeterJobType.CRON.getCode()) || Objects.equals(jobType, JmeterJobType.BY_TIME.getCode())) {

            if (relatedJob != null) {
                jobService.deleteJobByIds(String.valueOf(relatedJob.getJobId()));
            }

            Job job = new Job();
            job.setJobName("jmeterCronTask");
            job.setJobGroup("JMETER自动化任务调度");
            job.setMethodName("runTask");
            job.setMethodParams(String.valueOf(planId));
            job.setMisfirePolicy(ScheduleConstants.MISFIRE_DO_NOTHING);
            job.setStatus(ScheduleConstants.Status.NORMAL.getValue());
            job.setRemark("");
            job.setCronExpression(getCronExpr(jmeterTestPlan, jobType));
            jobService.insertJobCron(job);

            jmeterTestPlanService.combineJobAndPlan(job.getJobId(), planId);

        } else {

            //不再需要cronJob 删除
            if (relatedJob != null) {
                jobService.deleteJob(relatedJob);
            }
        }

        return toAjax(i);
    }

    private String getCronExpr(JmeterTestPlan jmeterTestPlan, Integer jobType) {
        //转换定时时间为cron表达式
        if (Objects.equals(jobType, JmeterJobType.BY_TIME.getCode())) {
            Date scheduledTime = jmeterTestPlan.getScheduledTime();
            if (scheduledTime != null) {
                String cronExpr = DateFormatUtils.format(scheduledTime, DATE_CRON_FOMATTER);
                return cronExpr;
            }

        } else {
            return jmeterTestPlan.getCronExpr();
        }
        return "";
    }

    /**
     * 删除jmx测试计划
     */
    @RequiresPermissions("jmeter:jmeterTestPlan:remove")
    @Log(title = "jmx测试计划", businessType = BusinessType.DELETE)
    @PostMapping("/remove")
    @ResponseBody
    public AjaxResult remove(String ids) {
        if (StringUtils.isEmpty(ids)) {
            return AjaxResult.error("id为空");
        }

        String[] array = Convert.toStrArray(ids);

        for (String planIdStr : array) {
            Job job = jmeterTestPlanService.getRelatedJobByPlanId(Long.valueOf(planIdStr));
            if (job != null) {
                jobService.deleteJob(job);
            }
        }

        return toAjax(jmeterTestPlanService.deleteJmeterTestPlanByIds(ids));
    }


    /**
     * 删除性能测试分布式节点
     */
    @PostMapping("/startJmeters")
    @ResponseBody
    public AjaxResult startJmeters(@RequestBody List<Long> ids) {

        if (CollectionUtils.isEmpty(ids)) {
            return AjaxResult.error("参数错误");
        }

        doExecuteTestPlans(ids);

        return AjaxResult.success();
    }

    public AjaxResult doExecuteTestPlans(List<Long> ids) {
        if (CollectionUtils.isEmpty(ids)) {
            return AjaxResult.error("参数错误");
        }

        List<JmeterTestPlan> jmeterTestPlans = jmeterTestPlanService.selectByIdList(ids);

        if (CollectionUtils.isEmpty(jmeterTestPlans)) {
            return AjaxResult.error("没有要执行的测试计划");
        }

        for (JmeterTestPlan jmeterTestPlan : jmeterTestPlans) {

            try {

                //查找用例是否有前驱用例，构造出一个测试用例链表
                LinkedList<JmeterTestCase> chainCases = getPlanCaseChain(jmeterTestPlan);
                //必要提交检查
                checkBeforeExecute(jmeterTestPlan, chainCases);

                executeTestCaseChain(chainCases, jmeterTestPlan);

            } catch (Exception e) {
                log.error("startJmeters 出错 planids={}", ids, e);
                throw new RuntimeException(e.getMessage());
            }
        }

        return AjaxResult.success();
    }

    private void checkBeforeExecute(JmeterTestPlan jmeterTestPlan, LinkedList<JmeterTestCase> planCaseChain) {

        if (planCaseChain.isEmpty()) {
            throw new RuntimeException("测试计划【" + jmeterTestPlan.getName() + "】没有找到有效的测试用例");
        }


        for (JmeterTestCase testCase : planCaseChain) {

            try {

                File script = checkJmxScript(testCase);

                if (script == null) {
                    RuntimeException exception = new RuntimeException("测试用例【" + testCase.getCaseName() + "】脚本不存在");
                    throw exception;
                }

                String nodeIds = testCase.getNodeIds();
                if (StringUtils.isEmpty(nodeIds)) {
                    RuntimeException exception = new RuntimeException("测试用例【" + testCase.getCaseName() + "】没有分配节点");
                    throw exception;
                }

                if (!Objects.equals(jmeterTestPlan.getRelProjectId(), testCase.getRelProjectId().intValue())) {
                    throw new RuntimeException("测试用例【" + testCase.getCaseName() + "】与测试计划的项目组不同");
                }

                checckNodeStatus(testCase.getNodeIds());

            } catch (Exception ex) {
				jmeterTestPlanRecordService.saveRecord(jmeterTestPlan, testCase, JmeterPlanRecordStatus.NOT_STARTED);
                throw new RuntimeException(ex.getMessage());
            }
        }
    }

    /**
     * 链式执行 测试用例
     *
     * @param cases
     * @param jmeterTestPlan
     */
    private void executeTestCaseChain(List<JmeterTestCase> cases, JmeterTestPlan plan) {

        Long planId = plan.getId();

        TestPlanListener testPlanListener = getTestPlanListener(cases, plan, planId);
        /**
         * 根据分配节点，尽量并行执行
         */
        runCaseTasksInParallel(cases, plan, testPlanListener);

    }



    private void runCaseTasksInParallel(List<JmeterTestCase> cases, JmeterTestPlan plan, TestPlanListener testPlanListener) {

        //taskList index==> taskList
        List<PlanCaseTaskGroup> taskGroups = generateCaseTaskGroup(cases, testPlanListener, plan);

        for (PlanCaseTaskGroup taskGroup : taskGroups) {
            threadPoolExecutor.submit(() -> {
                taskGroup.run();
            });
        }
    }

    private TestPlanListener getTestPlanListener(List<JmeterTestCase> cases, JmeterTestPlan jmeterTestPlan, Long planId) {

        JmeterTestPlanMetric metric = saveMetric(jmeterTestPlan);

        TestPlanListener testPlanListener = new TestPlanListener();
        testPlanListener.setTestPlan(jmeterTestPlan);
        testPlanListener.setLeftSize(new AtomicInteger(cases.size()));


        return testPlanListener;
    }



    private List<PlanCaseTaskGroup> generateCaseTaskGroup(List<JmeterTestCase> cases, TestPlanListener testPlanListener,
                                                          JmeterTestPlan plan ) {

        Map<Integer, List<PlanCaseTask>> map = new HashMap<>();

        Set<Long> nodeIds = new HashSet<>();

        int index = 0;

        for (JmeterTestCase testCase : cases) {

            if (StringUtils.isNotEmpty(testCase.getNodeIds())) {

                //是否已经被使用了
                boolean used = nodeBeenUsed(nodeIds, testCase);

                //如果被使用了 则需要等待，把任务追加到上一个任务队列中
                if (used) {

                    List<PlanCaseTask> planCaseTasks = map.get(index);
                    if (planCaseTasks == null) {
                        planCaseTasks = new ArrayList<>();
                    }
                    planCaseTasks.add(new PlanCaseTask(jmeterConfig, plan, testPlanListener, testCase));

                    //如果 没有被使用 则无需等待，开启新的队列 立即执行
                } else {

                    List<PlanCaseTask> planCaseTasks = new ArrayList<>();
                    planCaseTasks.add(new PlanCaseTask(jmeterConfig, plan, testPlanListener, testCase));
                    map.put(++index, planCaseTasks);
                }
            }
        }

        List<PlanCaseTaskGroup> groups = new ArrayList<>();

        for (Map.Entry<Integer, List<PlanCaseTask>> queue : map.entrySet()) {

            List<PlanCaseTask> planCaseTasks = queue.getValue();

            PlanCaseTaskGroup planCaseTaskGroup = new PlanCaseTaskGroup();
            planCaseTaskGroup.setPlanCaseTasks(planCaseTasks);
            groups.add(planCaseTaskGroup);
        }

        return groups;
    }

    private boolean nodeBeenUsed(Set<Long> nodeIds, JmeterTestCase testCase) {
        List<Long> ids = JSON.parseArray(testCase.getNodeIds(), Long.class);

        boolean contains = false;

        for (Long id : ids) {

            if (nodeIds.contains(id)) {
                contains = true;
            }

            nodeIds.add(id);
        }
        return contains;
    }



    private File checkJmxScript(JmeterTestCase testCase) {
        String scriptLocation = testCase.getScriptLocation();
        File script = new File(scriptLocation);
        if (!script.exists()) {
            log.error("jmx文件不存在 testCase={} location={}", testCase.getCaseName(), scriptLocation);
            return null;
        }
        return script;
    }


    private JmeterTestPlanMetric saveMetric(JmeterTestPlan jmeterTestPlan) {

        JmeterTestPlanMetric metric = new JmeterTestPlanMetric();
        metric.setPlanId(jmeterTestPlan.getId());
        metric.setPlanName(jmeterTestPlan.getName());
        metric.setStatus(JmeterPlanRecordStatus.EXECUTING.getCode());
        metric.setStartTime(new Date());
        metric.setCreateTime(new Date());
        metric.setEndTime(new Date());
        metric.setSn("" + SeqGenerator.nextId(1));

        jmeterTestPlanMetricService.insertJmeterTestPlanMetric(metric);
        return metric;
    }


    private void checckNodeStatus(String nodeIds) {

        List<Long> nodeIdList = JSON.parseArray(nodeIds, Long.class);

        List<JmeterNode> jmeterNodes = jmeterNodeService.selectByIdList(nodeIdList);

        List<JmeterNode> runningJmeters = new ArrayList<>();

        for (JmeterNode jmeterNode : jmeterNodes) {

            if (!Objects.equals(jmeterNode.getStatus(), JmeterNodeStatus.UP.getCode())) {
                log.error("性能测试分布式节点 JmeterNode is not running, node={}", jmeterNode.getNodeName());
                throw new RuntimeException("性能测试分布式节点 JmeterNode is not running, node=" + jmeterNode.getNodeName());
            }

            JmeterNodeJobRel jmeterNodeJobRel = jmeterNodeJobRelService.selectByNodeId(jmeterNode.getId());

            if (jmeterNodeJobRel != null) {
                String errorMsg = "节点【" + jmeterNode.getNodeName() + "】正在执行测试计划【" + jmeterNodeJobRel.getPlanName() + "】中的测试用例【" + jmeterNodeJobRel.getCaseName() + "】";
                log.error(errorMsg);
                throw new RuntimeException(errorMsg);
            }

            runningJmeters.add(jmeterNode);
        }

        if (CollectionUtils.isEmpty(runningJmeters)) {
            log.error("性能测试分布式节点 no running jmeter nodes");
            RuntimeException exception = new RuntimeException("性能测试分布式节点 no running jmeter nodes");
            throw exception;
        }

    }


    private LinkedList<JmeterTestCase> getPlanCaseChain(JmeterTestPlan jmeterTestPlan) {

        List<JmeterPlanCaseRel> caseRelList = jmeterPlanCaseRelService.listByPlanId(jmeterTestPlan.getId());

        if (CollectionUtils.isEmpty(caseRelList)) {
            log.warn("plan {} has empty testcases", jmeterTestPlan.getId());
            return new LinkedList<>();
        }


        LinkedList<JmeterTestCase> fullChain = new LinkedList<>();

        for (JmeterPlanCaseRel planCaseRel : caseRelList) {

            JmeterTestCase testCase = jmeterTestCaseService.selectJmeterTestCaseById(planCaseRel.getRelCaseId());

            try {
                handlePrevCases(testCase, new LinkedList<>(), new ArrayList<>());
            } catch (TestCaseCirculaException e) {
                throw new RuntimeException("测试计划【" + jmeterTestPlan.getName() + "】中的【" + testCase.getCaseName() + "】用例存在循环设置，请检查配置！");
            }

            if (testCase == null) {
                log.warn("测试用例【{}】已不存在", planCaseRel.getRelCaseId());
            } else {
                LinkedList<JmeterTestCase> singleChain = builderTestCaseChain(testCase);
                fullChain.addAll(singleChain);
            }
        }

        return fullChain;
    }


    private LinkedList<JmeterTestCase> builderTestCaseChain(JmeterTestCase testCase) {

        LinkedList<JmeterTestCase> tmp = new LinkedList<>();
        tmp.add(testCase);
        JmeterTestCase prevCase = testCase.getPrevCase();
        while (prevCase != null) {
            tmp.addFirst(prevCase);
            prevCase = prevCase.getPrevCase();
        }

        return tmp;
    }


    private void handlePrevCases(JmeterTestCase testCase, LinkedList<JmeterTestCase> caseLinkedList, List<Long> caseIdList) throws TestCaseCirculaException {
        if (testCase == null) {
            return;
        }

        caseIdList.add(testCase.getId());

        Long prevCaseId = testCase.getPrevCaseId();
        if (prevCaseId == null || prevCaseId == 0) {
            return;
        }

        if (caseIdList.contains(prevCaseId)) {
            throw new TestCaseCirculaException();
        }


        JmeterTestCase prevTestCase = jmeterTestCaseService.selectJmeterTestCaseById(prevCaseId);
        if (prevTestCase != null) {
            testCase.setPrevCase(prevTestCase);
        }

        handlePrevCases(prevTestCase, caseLinkedList, caseIdList);
    }

}
